#ifndef XG_HTTPRESPONSE_CPP
#define XG_HTTPRESPONSE_CPP
////////////////////////////////////////////////////////
#include "../zlib/zlib.h"
#include "../HttpServer.h"
#include "../HttpResponse.h"

const char* GetHttpStatusMessage(int status)
{
	switch(status)
	{
		case 100: return "Continue";
		case 101: return "Witching Protocols";
		case 200: return "OK";
		case 201: return "Created";
		case 202: return "Accepted";
		case 203: return "Non-Authoritative Information";
		case 204: return "No Content";
		case 205: return "Reset Content";
		case 206: return "Partial Content";
		case 300: return "Multiple Choices";
		case 301: return "Moved Permanently";
		case 302: return "Found";
		case 303: return "See Other";
		case 304: return "Not Modified";
		case 305: return "Use Proxy";
		case 307: return "Temporary Redirect";
		case 400: return "Bad Request";
		case 401: return "Unauthorized";
		case 402: return "Payment Required";
		case 403: return "Forbidden";
		case 404: return "Not Found";
		case 405: return "Method Not Allowed";
		case 406: return "Not Acceptable";
		case 407: return "Proxy Authentication Required";
		case 408: return "Request Timeout";
		case 409: return "Conflict";
		case 410: return "Gone";
		case 411: return "Length Required";
		case 412: return "Precondition Failed";
		case 413: return "Request Entity Too Large";
		case 414: return "Request URI Too Large";
		case 415: return "Unsupported Media Type";
		case 416: return "Requested Range Not Satisfiable";
		case 417: return "Expectation Failed";
		case 500: return "Internal Server Error";
		case 501: return "Not Implemented";
		case 502: return "Bad Gateway";
		case 503: return "Service Unavailable";
		case 504: return "Gateway Timeout";
		case 505: return "HTTP Version Not Supported";
		default: return "Unkown Error";
	}
}

bool HttpResponse::setContentType(const string& val)
{
	contype = val;

	if (stdx::startwith(val, "text") && val.find("charset") == string::npos)
	{
		return node.setValue("Content-Type", val + ";charset=" + HTTP_CHARSET);
	}
	else
	{
		return node.setValue("Content-Type", val);
	}
}
void HttpResponse::close()
{
	node.clear();
	request = NULL;
}
string HttpResponse::getHeadValue(const string& key) const
{
	string val = node.getValue(key);
	
	return val.empty() ? node.getValue(stdx::tolower(key)) : val;
}
bool HttpResponse::setHeadValue(const string& key, const string& val)
{
	return key == "content-type" || key == "Content-Type" ? setContentType(val) : node.setValue(key, val);
}
bool HttpResponse::init(HttpRequest* request, sp<Socket> sock, const string& addr)
{
	close();

	this->sock = sock;
	this->request = request;

	size_t pos = addr.rfind(':');

	sockhost = pos == string::npos ? addr : addr.substr(0, pos);

	if (request->getMethod() == eHEAD) return true;

	const string& url = request->getPath();
	const char* end = strrchr(url.c_str(), '.');

	if (end == NULL)
	{
		CHECK_FALSE_RETURN(setContentType("text/html"));
	}
	else
	{
		string fmt = end + 1;
		string val = HttpServer::Instance()->getMimeType(stdx::tolower(fmt));
		
		if (val.length() > 0)
		{
			CHECK_FALSE_RETURN(setContentType(val));
		}
		else if (fmt == "js")
		{
			CHECK_FALSE_RETURN(setContentType("text/javascipt"));
		}
		else if (fmt == "css")
		{
			CHECK_FALSE_RETURN(setContentType("text/css"));
		}
		else if (fmt == "png")
		{
			CHECK_FALSE_RETURN(setContentType("image/png"));
		}
		else if (fmt == "bmp")
		{
			CHECK_FALSE_RETURN(setContentType("image/bmp"));
		}
		else if (fmt == "gif")
		{
			CHECK_FALSE_RETURN(setContentType("image/gif"));
		}
		else if (fmt == "ico")
		{
			CHECK_FALSE_RETURN(setContentType("image/ico"));
		}
		else if (fmt == "htm" || fmt == "html")
		{
			CHECK_FALSE_RETURN(setContentType("text/html"));
		}
		else if (fmt == "jpg" || fmt == "jpeg")
		{
			CHECK_FALSE_RETURN(setContentType("image/jpeg"));
		}
		else if (fmt == "doc" || fmt == "docx")
		{
			CHECK_FALSE_RETURN(setContentType("application/msword"));
		}
		else if (fmt == "xls" || fmt == "xlsx")
		{
			CHECK_FALSE_RETURN(setContentType("application/vnd.ms-excel"));
		}
		else if (fmt == "ppt" || fmt == "pptx")
		{
			CHECK_FALSE_RETURN(setContentType("application/vnd.ms-powerpoint"));
		}
		else if (fmt == "cache" || fmt == "appcache")
		{
			CHECK_FALSE_RETURN(setContentType("text/cache-manifest"));
		}
		else
		{
			CHECK_FALSE_RETURN(setContentType("text/html"));
		}
	}

	return true;
}
string HttpResponse::getSocketHost() const
{
	return sockhost;
}
string HttpResponse::getClientHost() const
{
	string host = request->getHeadValue("X-Forwarded-For");

	if (host.empty()) return sockhost;

	size_t pos = host.find(',');

	return pos == string::npos ? host : host.substr(0, pos);
}
sp<Socket> HttpResponse::getSocket() const
{
	return sock;
}
string HttpResponse::getContentType() const
{
	return contype;
}
string HttpResponse::getHeadString() const
{
	return stdx::format("HTTP/1.1 %d %s\r\n%s\r\n\r\n", status, GetHttpStatusMessage(status), node.toString().c_str());
}
SmartBuffer HttpResponse::getRefusedContent(bool withead)
{
	SmartBuffer content;
	const CgiMapData item = HttpServer::Instance()->getCgiMapData("res/err/refused.htm");

	if (item.url.empty() || HttpServer::Instance()->getFileContent(item.url, content) <= 0)
	{
		content = "resource refused";
	}

	setHeadValue("Content-Length", stdx::str(content.size()));
	setHeadValue("Content-Type", "text/html");

	if (withead)
	{
		setStatus(403);

		string head = getHeadString();
		SmartBuffer data(head.length() + content.size());

		memcpy(data.str(), head.c_str(), head.length());
		memcpy(data.str() + head.length(), content.str(), content.size());

		return data;
	}

	return content;
}
SmartBuffer HttpResponse::getNotFoundContent(bool withead)
{
	SmartBuffer content;
	const CgiMapData item = HttpServer::Instance()->getCgiMapData("res/err/notfound.htm");

	if (item.url.empty() || HttpServer::Instance()->getFileContent(item.url, content) <= 0)
	{
		content = "resource not found";
	}

	setHeadValue("Content-Length", stdx::str(content.size()));
	setHeadValue("Content-Type", "text/html");

	if (withead)
	{
		setStatus(404);

		string head = getHeadString();
		SmartBuffer data(head.length() + content.size());

		memcpy(data.str(), head.c_str(), head.length());
		memcpy(data.str() + head.length(), content.str(), content.size());

		return data;
	}

	return content;
}
int HttpResponse::forward(const string& url, const string& addr, function<int(const void*, int, const string&)> response)
{
	static string nokey = "[NOT FOUND]";
	static LogThread* log = LogThread::Instance();
	static HttpServer* app = HttpServer::Instance();

	if (url.empty()) return XG_DATAERR;

	int val = 0;
	CgiMapData& item = request->cgidata;
	const string& key = CgiMapData::GetKey(url);
	const vector<string>& alloworiginlist = app->alloworiginlist;

	item = app->getCgiMapData(url);

	if (item.access == 0) return XG_AUTHFAIL;

	if (item.url.empty())
	{
		if (app->monitor > 0)
		{
			SpinLocker lk(app->transmtx);
			app->transmap[nokey].daily++;
		}

		return XG_NOTFOUND;
	}

	if (alloworiginlist.size() > 0)
	{
		string origin = request->getHeadValue("Origin");
		string host = CgiMapData::GetKey(origin);

		if (stdx::startwith(host, "https://"))
		{
			host = host.substr(8);
		}
		else if (stdx::startwith(host, "http://"))
		{
			host = host.substr(7);
		}

		for (const string& item : alloworiginlist)
		{
			if (item == "*" || item == "all" || item == host)
			{
				string head = request->getHeadValue("Access-Control-Request-Headers");

				if (head.length() > 0) setHeadValue("Access-Control-Allow-Headers", head);

				setHeadValue("Access-Control-Allow-Origin", origin);
				setHeadValue("Access-Control-Allow-Credentials", "true");

				if (request->getMethod() == eOPTION)
				{
					origin.clear();

					setHeadValue("Access-Control-Allow-Method", "GET,POST,OPTIONS");

					return response(origin.c_str(), origin.length(), addr);
				}

				break;
			}
		}
	}

	if (item.param.length() > 0)
	{
		HttpDataNode data;

		if (data.parse(item.param))
		{
			const auto& datmap = data.getDataMap();

			for (const auto& elem : datmap)
			{
				if (request->getParameter(elem.first).empty())
				{
					request->setParameter(elem.first, stdx::DecodeURL(elem.second));
				}
			}
		}
	}

	if (item.flag == CgiMapData::IDX_FLAG)
	{
		string file;
		string path;
		string folder;

		if (key == "index")
		{
			path = "/";
		}
		else
		{
			folder = "<a href='../'>../</a>\r\n";
			path = "/" + url + "/";
		}

		stdx::GetFolderContent([&](string name, int type, long size, time_t mtime){
			if (type == eFILE)
			{
				string line = stdx::format("<a href='%s%s' download='%s'>%s</a>", path.c_str(), name.c_str(), name.c_str(), name.c_str());
				line = stdx::fill(line, 96 + name.length() * 2 + path.length(), false, ' ') + " " + DateTime(mtime).toString() + "\t\t" + stdx::str(size);
				file += line + "\r\n";
			}
			else
			{
				string line = stdx::format("<a href='%s%s/'>%s/</a>", path.c_str(), name.c_str(), name.c_str());
				line = stdx::fill(line, 85 + name.length() + path.length(), false, ' ') + " " + DateTime(mtime).toString();
				folder += line + "\r\n";
			}
		}, item.url);

		string css = "a{line-height:1.2rem;text-decoration:none}div{padding:4vh 10vw}";
		string html = stdx::format("<html><head><title>%s</title><style>%s</style></head><body><div>%s</br><hr/><pre>%s%s</pre><hr/></div></body></html>", url.c_str(), css.c_str(), path.c_str(), folder.c_str(), file.c_str());
	
		return response(html.c_str(), html.length(), addr);
	}

	auto doWork = [&](){
		int val = 0;

		if (item.code == CgiMapData::GZIP_CODE) setHeadValue("Content-Encoding", "gzip");

		if (item.flag == CgiMapData::CGI_FLAG)
		{
			sp<DllFile> dll;
			const MemFile* file = NULL;
			HttpProcessBase* cgi = NULL;
			CREATE_HTTP_CGI_FUNC create_cgi = item.create_cgi;
			DESTROY_HTTP_CGI_FUNC destroy_cgi = item.destroy_cgi;

			if (create_cgi == NULL || destroy_cgi == NULL)
			{
				dll = newsp<DllFile>();

				if (dll->open(item.url))
				{
					dll->read(create_cgi, "CreateHttpProcessObject");
					dll->read(destroy_cgi, "DestroyHttpProcessObject");

					if (create_cgi == NULL || destroy_cgi == NULL) return XG_SYSERR;
				}
				else
				{
					return XG_NOTFOUND;
				}
			}

			if ((cgi = create_cgi()) == NULL) return XG_SYSERR;

			val = cgi->doWork(request, this);

			if (val < 0)
			{
				destroy_cgi(cgi);

				return val;
			}

			if (file = cgi->getOutFile())
			{
				val = response(file->getData(), file->size(), addr);
			}
			else
			{
				const string& msg = cgi->getOutString();

				val = response(msg.c_str(), msg.length(), addr);
			}

			destroy_cgi(cgi);
		}
		else
		{
			time_t utime;
			SmartBuffer content;
			int len = app->getFileContent(item.url, content, utime);

			if (len < 0) return XG_NOTFOUND;

			if (len == 0)
			{
				const string& msg = stdx::EmptyString();

				return response(msg.c_str(), msg.length(), addr);
			}

			const char* msg = content.str();

			if (len > 0xFF)
			{
				char etag[64];
				string version = request->getHeadValue("If-None-Match");

				sprintf(etag, "%ld:%d", utime, len);

				if (version == etag)
				{
					setStatus(304);
					msg = "";
					len = 0;
				}

				setHeadValue("ETag", etag);
			}

			if (msg == NULL)
			{
				msg = item.url.c_str();
				len = -1;
			}

			return response(msg, len, addr);
		}

		return val;
	};

	if (app->monitor <= 0) return doWork();

	if (item.hostmaxcnt > 0)
	{
		string host = getClientHost();

		if (host.empty()) host = addr;

		val = GetHostInteger(host.c_str());

		app->hostmtx.lock();

		val = app->hostmap[key].check(val, item.hostmaxcnt);
	
		app->hostmtx.unlock();

		if (val > 0)
		{
			LogTrace(eIMP, "restrict[%s][%s] access[%d]", host.c_str(), url.c_str(), val);

			return XG_AUTHFAIL;
		}
	}

	app->transmtx.lock();

	auto& realtime = app->transmap[key].realtime;

	if (item.maxcnt > 0 && realtime >= item.maxcnt)
	{
		LogTrace(eIMP, "concurrent[%s][%s] limit[%d]", url.c_str(), addr.c_str(), realtime);

		app->transmtx.unlock();

		return XG_AUTHFAIL;
	}

	realtime++;

	app->transmtx.unlock();

	auto us = GetTime();

	if ((val = doWork()) == XG_NOTFOUND)
	{
		if (item.flag == CgiMapData::URL_FLAG) app->cgimap.remove(key);

		SpinLocker lk(app->transmtx);
		app->transmap[nokey].daily++;
		app->transmap.erase(key);
		
		return XG_NOTFOUND;
	}

	us = GetTime() - us;

	app->transmtx.lock();

	auto& tran = app->transmap[key];

	if (us < tran.mincost) tran.mincost = us;
	if (us > tran.maxcost) tran.maxcost = us;

	if (tran.daily < 9)
	{
		tran.meancost = (tran.meancost * tran.daily + us) / (tran.daily + 1);
	}
	else
	{
		tran.meancost = (tran.meancost * 9 + us) / 10;
	}

	tran.realtime--;
	tran.daily++;

	app->transmtx.unlock();

	LogTrace(eDBG, "request[%s][%s] cost %ld usec", addr.c_str(), url.c_str(), us);

	return val;
}
void HttpResponse::addCookie(const string& key, const string& val, int timeout)
{
	string msg = key + "=" + val;

	if (timeout > 0) msg += ";max-age=" + stdx::str(timeout) + ";HttpOnly";

	node.setValue("Set-Cookie:" + key, msg);
}

static SmartBuffer GetData(HttpResponse* response)
{
	SmartBuffer data = response->getResult();
	string encode = response->getHeadValue("Content-Encoding");

	if (stdx::tolower(encode) == "gzip")
	{
		u_int32 len = HTTP_RESDATA_MAXSIZE;
		SmartBuffer buffer(HTTP_RESDATA_MAXSIZE + sizeof(wchar_t));

		if (GZIPDecompress(data.str(), data.size(), buffer.str(), &len) < 0)
		{
			buffer.free();
		}
		else
		{
			buffer.truncate(len);
			data = buffer;
		}
	}

	return data;
}
static bool IsEndChunkedData(const char* buffer, int readed)
{
	static u_char FOOTER[7] = {0x0D, 0x0A, 0x30, 0x0D, 0x0A, 0x0D, 0x0A};

	if (memcmp(buffer + readed - 3, FOOTER, 3) == 0) return true;
	if (memcmp(buffer + readed - sizeof(FOOTER), FOOTER, sizeof(FOOTER)) == 0) return true;

	return false;
}
static const char* GetChunkedDataSize(const char* src, int& sz)
{
	int val = 0;
	int sum = 0;
	const char* str = src;

	if (memcmp(str, "\r\n", 2) == 0)
	{
		str += 2;

		int idx = 0;

		while (*str && idx < 8)
		{
			if (*str >= '0' && *str <= '9')
			{
				val = *str - '0';
			}
			else if (*str >= 'a' && *str <= 'f')
			{
				val = *str - 'a' + 10;
			}
			else if (*str >= 'A' && *str <= 'F')
			{
				val = *str - 'A' + 10;
			}
			else
			{
				break;
			}

			sum *= 16;
			sum += val;

			++idx;
			++str;
		}

		if (idx < 8)
		{
			sz = sum;

			return str;
		}
	}

	return NULL;
}
static SmartBuffer GetTranslateChunkedData(const char* src, int len)
{
	struct ChunkedElement
	{
		int len;
		const char* data;
		ChunkedElement(const char* str, int sz) : data(str), len(sz){}
	};

	int sz = 0;
	int offset = 0;
	SmartBuffer data;
	const char* str = src;
	const char* end = NULL;
	vector<ChunkedElement> vec;

	while (memcmp(str, "\r\n", 2) == 0)
	{
		if ((end = GetChunkedDataSize(str, sz)) == NULL) break;
		if (memcmp(end, "\r\n", 2)) break;
		if (str + 2 >= src + len) break;
		if (sz == 0) break;

		end += 2;
		
		vec.push_back(ChunkedElement(end, sz));
		str = end + sz;
		offset += sz;
	}

	if (sz == 0)
	{
		data.malloc(offset);
		offset = 0;

		for (auto& item : vec)
		{
			memcpy(data.str() + offset, item.data, item.len);
			offset += item.len;
		}
	}

	return data;
}

int HttpResponse::init(sp<Socket> sock, bool decode, int timeout)
{
	string str;
	int len = 0;
	int readed = 0;
	Timer tr(NULL);
	SmartBuffer data;
	char* end = NULL;
	char* buffer = NULL;
	long maxcost = 1000L * timeout;
	int maxsz = HTTP_RESDATA_MAXSIZE;
	
	this->close();
	this->sock = sock;
	buffer = (char*)data.malloc(maxsz);

	while (readed < maxsz)
	{
		if ((len = sock->read(buffer + readed, maxsz - readed, false)) == 0)
		{
			if (tr.getTimeGap() > maxcost) return XG_TIMEOUT;

			Sleep(SOCKECT_RECVTIMEOUT);

			continue;
		}

		if (len < 0) return len;

		buffer[readed += len] = 0;

		end = strstr(buffer, HTTP_REQHDR_ENDSTR);

		if (end == NULL)
		{
			if (readed >= maxsz) return XG_DATAERR;
		}
		else
		{
			*end = 0;
			
			if (!parseHeader(buffer)) return XG_DATAERR;

			*end = *HTTP_REQHDR_ENDSTR;
			end += HTTP_REQHDR_ENDSTR_LEN;

			long long sz = 0;
			int hdrsz = end - buffer;

			if ((str = getHeadValue("Content-Length")).length() > 0 && (sz = stdx::atol(str.c_str())) == 0) return hdrsz;

			if (sz < 0 || sz > XG_MEMFILE_MAXSZ) return XG_DATAERR;

			if (sz > 0)
			{
				buffer = (char*)data.truncate(maxsz = hdrsz + sz);

				if (maxsz > readed)
				{
					if ((len = sock->read(buffer + readed, maxsz - readed)) < 0) return len;

					readed += len;
				}

				memcpy(result.malloc(sz), buffer + hdrsz, sz);

				if (decode) result = GetData(this);
			}
			else
			{
				len = readed;

				while (readed < maxsz)
				{
					if (len > 0 && IsEndChunkedData(buffer, readed)) break;

					if ((len = sock->read(buffer + readed, maxsz - readed, false)) == 0)
					{
						if (tr.getTimeGap() > maxcost) return XG_TIMEOUT;

						Sleep(SOCKECT_RECVTIMEOUT);

						continue;
					}

					if (len < 0) return len;

					readed += len;
				}

				if (readed >= maxsz) return XG_DATAERR;

				buffer[readed] = 0;
				readed -= hdrsz;
				hdrsz -= 2;

				if (readed == 5) return XG_FAIL;

				result = GetTranslateChunkedData(buffer + hdrsz, readed);

				if (result.isNull()) return XG_DATAERR;

				if (decode) result = GetData(this);
			}

			return readed;
		}
	}

	if (readed >= maxsz) return XG_DATAERR;

	return readed;
}
bool HttpResponse::isKeepAlive() const
{
	string keepalive = getHeadValue("Connection");

	if (keepalive.empty())
	{
		if (request == NULL) return false;

		keepalive = request->getHeadValue("Connection");
	}

	return stdx::startwith(stdx::tolower(keepalive), "keep-alive");
}
SmartBuffer HttpResponse::getResult() const
{
	return result;
}
bool HttpResponse::parseHeader(const char* msg)
{
	const char* str = strstr(msg, "\r\n");

	if (str == NULL || str == msg) return false;

	CHECK_FALSE_RETURN(parseHeader(msg, str - msg));
	CHECK_FALSE_RETURN(node.parse(str + 2));

	return true;
}
bool HttpResponse::parseHeader(const char* msg, int len)
{
	const char* str = msg;
	const char* end = NULL;
	
	while (*str && isspace(*str)) str++;

	CHECK_FALSE_RETURN(end = strchr(str, ' '));

	while (*end && isspace(*end)) end++;
	
	str = end;
	
	CHECK_FALSE_RETURN(end = strchr(str, ' '));
	CHECK_FALSE_RETURN(end < msg + len);
	
	status = stdx::atoi(str);

	return true;
}
////////////////////////////////////////////////////////
#endif